package com.kamistoat.meimeichat.meimeichatclient.event;

import com.kamistoat.meimeichat.meimeichatclient.MeiMeiChatClientApplication;
import com.kamistoat.meimeichat.meimeichatclient.config.NettyClientConstant;
import com.kamistoat.meimeichat.meimeichatclient.infrastructure.util.BeanUtil;
import com.kamistoat.meimeichat.meimeichatclient.infrastructure.util.CacheUtil;
import com.kamistoat.meimeichat.meimeichatclient.kcp.P2PDatagramHandler;
import com.kamistoat.meimeichat.meimeichatui.view.chat.IChatEvent;
import com.kamistoat.meimeichatserveraggrement.protocol.Command;
import com.kamistoat.meimeichatserveraggrement.protocol.friend.AddFriendRequest;
import com.kamistoat.meimeichatserveraggrement.protocol.friend.SearchFriendRequest;
import com.kamistoat.meimeichatserveraggrement.protocol.msg.MsgGroupRequest;
import com.kamistoat.meimeichatserveraggrement.protocol.msg.MsgRequest;
import com.kamistoat.meimeichatserveraggrement.protocol.msg.MsgRoamingRequest;
import com.kamistoat.meimeichatserveraggrement.protocol.p2p.UDP_EstablishP2PClient2Server;
import com.kamistoat.meimeichatserveraggrement.protocol.p2p.UDP_MessageClient2Client;
import com.kamistoat.meimeichatserveraggrement.protocol.talk.DelTalkRequest;
import com.kamistoat.meimeichatserveraggrement.protocol.talk.TalkNoticeRequest;
import com.kamistoat.meimeichatserveraggrement.util.SerializationUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.socket.DatagramPacket;
import javafx.scene.control.ListView;
import javafx.scene.layout.Pane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.channel.Channel;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Date;

public class ChatEvent implements IChatEvent {

    private Logger logger = LoggerFactory.getLogger(ChatEvent.class);

    @Override
    public void doQuit() {
        logger.info("退出登陆！");
        BeanUtil.getBean("Client2ServerChannel", Channel.class).close();
    }

    @Override
    public void doSendMsg(String userId, String userNickname, String userHead, String talkId,
                          Integer talkType, String msg, Integer msgType, Date msgDate) {
        // client2ServerTCPChannel是客户端与服务端保持的TCP链接，用于中转群聊消息或者无法建立P2P链接的私聊消息
        Channel client2ServerTCPChannel = BeanUtil.getBean("Client2ServerChannel", Channel.class);
        // clientP2PChannel是客户端与服务端进行UDP链接的通道。通过该通道将自身UDP地址传递给服务器，用于建立和其他客户端的P2P链接。
        Channel clientP2PChannel = BeanUtil.getBean("ClientP2PChannel", Channel.class);

        // 好友. 在线好友使用UDP打洞， 离线好友不做处理。无论是否离线，聊天记录都漫游服务器进行保存
        if (NettyClientConstant.TalkType.Friend.getCode().equals(talkType)) {
            // 无论怎样，都会向服务器漫游消息记录. 异步执行即可
            MeiMeiChatClientApplication.executorService.execute(new Runnable() {
                @Override
                public void run() {
                    client2ServerTCPChannel.writeAndFlush(new MsgRoamingRequest(userId, userNickname, userHead, talkId, msg, msgType, msgDate));
                    logger.info("消息已漫游至服务器...");
                }
            });

            // 如果p2pMap中不存在好友Id，说明好友根本不在线。则不打洞。如果p2pMap中存在好友Id，说明好友在线。
            // 如果在之前的尝试打洞中失败且未超时，则不再尝试打洞，但同时也 不会更新 p2pFailureDateMap
            if(CacheUtil.p2pFailureDateMap.containsKey(talkId) &&
                    new Date().getTime() - CacheUtil.p2pFailureDateMap.get(talkId).getTime() <= NettyClientConstant.P2P_TIMEOUT) {
                logger.info("当前无法与用户 {} 建立NET映射，对方路由器可能不支持P2P链接，将通过服务器进行消息转发...", talkId);
                client2ServerTCPChannel.writeAndFlush(new MsgRequest(userId, userNickname, userHead, talkId, msg, msgType, msgDate));
            }
            // 如果未建立打洞或者有效打洞已过期，则重新建立打洞
            else if (!CacheUtil.p2pDateMap.containsKey(talkId) ||
                    new Date().getTime() - CacheUtil.p2pDateMap.get(talkId).getTime() > NettyClientConstant.P2P_TIMEOUT) {

                logger.info("开始建立与好友 {} 的P2P连接...", talkId);

                // 向服务器发送 UDP_EstablishP2PClient2Server，要求与好友建立打洞。
                UDP_EstablishP2PClient2Server udp_establishP2PChannel2Server = new UDP_EstablishP2PClient2Server(userId, talkId);
                byte[] data1 = SerializationUtil.serialize(udp_establishP2PChannel2Server);
                ByteBuf byteBuf1 = Unpooled.buffer();
                byteBuf1.writeInt(data1.length + 1);
                byteBuf1.writeByte(Command.UDP_EstablishP2PClient2Server);
                byteBuf1.writeBytes(data1);
                clientP2PChannel.writeAndFlush(
                        new DatagramPacket(byteBuf1,
                                new InetSocketAddress(NettyClientConstant.NETTY_SERVER_P2P_HOST, NettyClientConstant.NETTY_SERVER_P2P_PORT)));
                logger.info("已通知服务器协助建立与好友 {} 的P2P连接...", talkId);

                // 等待P2P建立完成。每500ms检查一次p2pDateMap，共检查6次. 建立成功则直接通过P2P通道发送消息，如果for循环结束后无法建立，则不走P2P
                int totalWait = 6;
                for (int i = 0; i < totalWait; i++) {
                    if (CacheUtil.p2pDateMap.containsKey(talkId) &&
                            new Date().getTime() - CacheUtil.p2pDateMap.get(talkId).getTime() <= NettyClientConstant.P2P_TIMEOUT) {
                        InetSocketAddress friendAddress = (InetSocketAddress) CacheUtil.p2pMap.get(talkId);
                        UDP_MessageClient2Client udp_messageClient2Client = new UDP_MessageClient2Client(
                                userId, userNickname, userHead, talkId, msg, msgType, msgDate);
                        P2PDatagramHandler.sendUDPMessage2Client(clientP2PChannel, friendAddress, udp_messageClient2Client);
                        return;
                    } else {
                        try {
                            logger.info("建立P2P链接中...");
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                // 如果上面的for循环没有return而是正常结束，说明无法和对方建立UDP打洞，可能是对方的路由器并不支持UDP打洞。则使用服务器转发
                // 同时更新 p2pFailureDateMap
                logger.info("当前无法与用户 {} 建立NET映射，对方路由器可能不支持P2P链接，将通过服务器进行消息转发...", talkId);
                CacheUtil.p2pFailureDateMap.put(talkId, new Date());
                client2ServerTCPChannel.writeAndFlush(new MsgRequest(userId, userNickname, userHead, talkId, msg, msgType, msgDate));
            }
            // 否则直接向打洞中发送UDP消息
            else {
                logger.info("直接与用户 {} 进行P2P通信", talkId);
                InetSocketAddress friendAddress = (InetSocketAddress) CacheUtil.p2pMap.get(talkId);
                UDP_MessageClient2Client udp_messageClient2Client = new UDP_MessageClient2Client(
                        userId, userNickname, userHead, talkId, msg, msgType, msgDate);
                P2PDatagramHandler.sendUDPMessage2Client(clientP2PChannel, friendAddress, udp_messageClient2Client);
            }
        }
        // 群组.永远都是由服务器转发，不存在打洞的说法
        else if (NettyClientConstant.TalkType.Group.getCode().equals(talkType)) {
            client2ServerTCPChannel.writeAndFlush(new MsgGroupRequest(userId, userNickname, userHead, talkId, msg, msgType, msgDate));
        }
    }

    @Override
    public void doEventAddTalkUser(String userId, String userFriendId) {
        CacheUtil.talkMap.put(userFriendId, 1);
        Channel channel = BeanUtil.getBean("Client2ServerChannel", Channel.class);
        channel.writeAndFlush(new TalkNoticeRequest(userId, userFriendId, 0));
    }

    @Override
    public void doEventAddTalkGroup(String userId, String groupId) {
        CacheUtil.talkMap.put(groupId, 1);
        Channel channel = BeanUtil.getBean("Client2ServerChannel", Channel.class);
        channel.writeAndFlush(new TalkNoticeRequest(userId, groupId, 1));
    }

    @Override
    public void doEventDelTalkUser(String userId, String talkId) {
        CacheUtil.talkMap.remove(talkId);
        Channel channel = BeanUtil.getBean("Client2ServerChannel", Channel.class);
        channel.writeAndFlush(new DelTalkRequest(userId, talkId));
    }

    @Override
    public void addFriendLuck(String userId, ListView<Pane> listView) {
        Channel channel = BeanUtil.getBean("Client2ServerChannel", Channel.class);
        channel.writeAndFlush(new SearchFriendRequest(userId, ""));
    }

    @Override
    public void doFriendLuckSearch(String userId, String text) {
        Channel channel = BeanUtil.getBean("Client2ServerChannel", Channel.class);
        channel.writeAndFlush(new SearchFriendRequest(userId, text));
    }

    @Override
    public void doEventAddLuckUser(String userId, String friendId) {
        Channel channel = BeanUtil.getBean("Client2ServerChannel", Channel.class);
        channel.writeAndFlush(new AddFriendRequest(userId, friendId));
    }
}
